//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Threading;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;

    /// <summary>
    /// ScrollViewer with implementation for scrolling when the drop target is outside of the visible area.
    /// </summary>
    public class DragDropScrollViewer : ScrollViewer
    {
        /// <summary>
        /// Milliseconds.
        /// </summary>
        private const double dragInterval = 10;

        /// <summary>
        /// Pixels per millisecond.
        /// </summary>
        private const double dragInitialVelocity = 0.5;

        /// <summary>
        /// Drag margin for upper and lower work limits.
        /// </summary>
        private static double dragMargin = 40.0;

        /// <summary>
        /// Scroll timer for the actual scrolling.
        /// </summary>
        private DispatcherTimer dragScrollTimer;

        /// <summary>
        /// The drag velocity.
        /// </summary>
        private double dragVelocity;

        /// <summary>
        /// Specifies the drag direction.
        /// </summary>
        private enum DragDirection
        {
            /// <summary>
            /// For dragging down.
            /// </summary>
            Down,

            /// <summary>
            /// For dragging up.
            /// </summary>
            Up,

            /// <summary>
            /// For dragging left.
            /// </summary>
            Left,

            /// <summary>
            /// For dragging right.
            /// </summary>
            Right
        };        

        /// <summary>
        /// Fired during drag when there is any change of state in the keyboard or the mouse.
        /// </summary>
        /// <param name="args">
        /// QueryContinueDragEventArgs.
        /// </param>
        protected override void OnPreviewQueryContinueDrag(QueryContinueDragEventArgs e)
        {
            base.OnPreviewQueryContinueDrag(e);
            if (e.Action == DragAction.Cancel || e.Action == DragAction.Drop)
            {
                this.CancelDrag();
            }
            else if (e.Action == DragAction.Continue)
            {
                Point pt = SafeNativeMethods.GetMousePosition(this);
                if ((pt.Y < DragDropScrollViewer.dragMargin) || (pt.Y > this.RenderSize.Height - DragDropScrollViewer.dragMargin)
                    || (pt.X < DragDropScrollViewer.dragMargin) || (pt.X > this.RenderSize.Width - DragDropScrollViewer.dragMargin))
                {
                    if (null == this.dragScrollTimer)
                    {
                        this.dragVelocity = DragDropScrollViewer.dragInitialVelocity;
                        this.dragScrollTimer = new DispatcherTimer();
                        this.dragScrollTimer.Tick += this.TickDragScroll;
                        this.dragScrollTimer.Interval = new TimeSpan(0, 0, 0, 0, (int)DragDropScrollViewer.dragInterval);
                        this.dragScrollTimer.Start();
                    }
                }
            }
        } 

        /// <summary>
        /// Scroll implementation for the scroller. Fired on a timer.
        /// </summary>
        /// <param name="sender">
        /// DragDropScrollViewer.
        /// </param>
        /// <param name="e">
        /// EventArgs.
        /// </param>
        private void TickDragScroll(object sender, EventArgs e)
        {
            bool isDone = true;
            if (this.IsLoaded)
            {
                Rect bounds = new Rect(this.RenderSize);
                Point pt = SafeNativeMethods.GetMousePosition(this);
                if (bounds.Contains(pt))
                {
                    if (pt.Y < DragDropScrollViewer.dragMargin)
                    {
                        this.DragScroll(DragDirection.Up);
                        isDone = false;
                    }
                    else if (pt.Y > this.RenderSize.Height - DragDropScrollViewer.dragMargin)
                    {
                        this.DragScroll(DragDirection.Down);
                        isDone = false;
                    }
                    else if (pt.X < DragDropScrollViewer.dragMargin)
                    {
                        this.DragScroll(DragDirection.Left);
                        isDone = false;
                    }
                    else if (pt.X > this.RenderSize.Width - DragDropScrollViewer.dragMargin)
                    {
                        this.DragScroll(DragDirection.Right);
                        isDone = false;
                    }
                }
            }

            if (isDone)
            {
                this.CancelDrag();
            }
        }

        /// <summary>
        /// Cancel the drag.
        /// </summary>
        private void CancelDrag()
        {
            if (this.dragScrollTimer != null)
            {
                this.dragScrollTimer.Tick -= this.TickDragScroll;
                this.dragScrollTimer.Stop();
                this.dragScrollTimer = null;
            }
        }

        /// <summary>
        /// Scrolls the content.
        /// </summary>
        /// <param name="direction">
        /// Direction in which to scroll.
        /// </param>
        private void DragScroll(DragDirection direction)
        {
            if (DragDirection.Up == direction || DragDirection.Down == direction)
            {
                bool isUp = (DragDirection.Up == direction);
                double verticalOffset = Math.Max(0.0, this.VerticalOffset + (isUp ? -(this.dragVelocity * DragDropScrollViewer.dragInterval) : (this.dragVelocity * DragDropScrollViewer.dragInterval)));
                this.ScrollToVerticalOffset(verticalOffset);
            }
            else
            {
                bool isLeft = (DragDirection.Left == direction);
                double horizontalOffset = Math.Max(0.0, this.HorizontalOffset + (isLeft ? -(this.dragVelocity * DragDropScrollViewer.dragInterval) : (this.dragVelocity * DragDropScrollViewer.dragInterval)));
                this.ScrollToHorizontalOffset(horizontalOffset);
            }
        }
    }
}

